In this analysis module, we have annotated cell types in
SCPCP000004 samples using two methods: SingleR
and scANVI/scArches, both using the NBAtlas dataset as a
reference. In this notebook, we derive the final annotations using
information from the SingleR labels, the
scANVI/scArches labels, and existing consensus cell type
annotations. All final annotations use labels present in the NBAtlas
dataset.
We use the following approach to determine the final annotation.
First, we prioritize inferences with SingleR and
scANVI/scArches. If these methods returned the exact same
label, we assign this label.
Next, we compare labels across all three methods: if at least two
out of three methods agree, we assign the agreeing label. Note that
this requires mapping consensus cell types to NBAtlas labels, which is
only possible for these cell types: endothelial cells, fibroblasts, and
immune cell types except for the NBAtlas labels RBCs and
pDC which do not have corresponding consensus labels.
In this approach, there are two ways that cell type annotations can
agree, and we treat them as follows:
- Cell type annotations can have directly corresponding labels. For
example:
- If the consensus cell type is
endothelial cell and at
least one of the NBAtlas-derived annotations is
Endothelial, we assign the label
Endothelial.
- Cell type annotations can differ but be in the same “family” of cell
types. For example:
- If the consensus cell type is
mature T cell and at
least one of the NBAtlas-derived annotations is
CD4+ T cell, we assign the broader label
T cell.
- If one NBAtlas-derived annotation is
Patrolling monocyte and the other is
Classical monocyte, we assign the broader label
Monocyte.
We use CL ontology ids to determine agreement throughout. For more
information about how these ontology ids were determined for NBAtlas
labels, see ../references/README.md.
For the final exported TSV file, we further designate any cells
labeled as Neuroendocrine as tumor, following
how this label is interpreted in NBAtlas.
Setup
suppressPackageStartupMessages({
library(ggplot2)
library(patchwork)
library(SingleCellExperiment)
})
theme_set(
theme_bw()
)
umap_theme <- list(
theme(
axis.text = element_blank(),
axis.ticks = element_blank(),
legend.position = "bottom",
aspect.ratio = 1
)
)
# settings
options(
dplyr.summarise.inform = FALSE,
readr.show_col_types = FALSE
)
Paths
module_dir <- rprojroot::find_root(rprojroot::is_renv_project)
repository_base <- file.path(module_dir, "..", "..")
data_dir <- file.path(repository_base, "data", "current", "SCPCP000004")
merged_dir <- file.path(repository_base, "data", "current", "results", "merge-sce", "SCPCP000004")
ref_dir <- file.path(module_dir, "references")
results_dir <- file.path(module_dir, "results")
singler_dir <- file.path(results_dir, "singler")
scanvi_dir <- file.path(results_dir, "scanvi")
# merged SCE file
sce_file <- file.path(
merged_dir,
"SCPCP000004_merged.rds"
)
# SingleR files
singler_files <- list.files(
path = singler_dir,
pattern = "_singler-annotations\\.tsv",
recursive = TRUE,
full.names = TRUE
) |>
# add names as library id
purrr::set_names(
\(x) {
stringr::str_remove(basename(x), "_singler-annotations.tsv")
}
)
# scanvi predictions
scanvi_file <- file.path(
scanvi_dir,
"scanvi-predictions.tsv"
)
# palette file
palette_file <- file.path(
module_dir,
"palettes",
"nbatlas-cell-type-palette.tsv"
)
# label map file
nbatlas_label_map_file <- file.path(
ref_dir,
"nbatlas-label-map.tsv"
)
# label ontologies file
nbatlas_ontology_file <- file.path(
ref_dir,
"nbatlas-ontology-ids.tsv"
)
# marker genes from NBAtlas
nbatlas_markers_file <- file.path(
ref_dir,
"nbatlas-marker-genes.tsv"
)
# validation groups URL, used to obtain consensus "family" ontologies (aka broad validation group ontologies)
validation_url <- "https://raw.githubusercontent.com/AlexsLemonade/OpenScPCA-analysis/refs/heads/main/analyses/cell-type-consensus/references/consensus-validation-groups.tsv"
Functions
# Source utilities functions:
# - subset_nbatlas_markers()
# - faceted_umap()
# - generate_dotplot()
source(file.path(module_dir, "scripts", "utils", "celltype-utils.R"))
#' Prepare data frame of scANVI or SingleR labels for annotation
#'
#' @param df Data frame to prepare
#' @param annot_type "singler" or "scanvi"
#' @param ontology_df Data frame of ontology ids
#' @param label_map_df Data frame mapping labels to family labels
#'
#' @returns Wide data frame with ontologies and family labels for the given annotation type
prep_for_annotation <- function(
df,
annot_type,
ontology_df,
label_map_df) {
df |>
dplyr::select(all_of(c("cell_id", annot_type))) |>
dplyr::rename(label = annot_type) |>
####### Join in the family labels
dplyr::left_join(label_map_df, by = c("label" = "NBAtlas_label")) |>
dplyr::rename(family = NBAtlas_family) |>
######### Obtain LABEL ontologies
dplyr::left_join(ontology_slim_df, by = c("label" = "NBAtlas_label")) |>
dplyr::rename(label_ontology = CL_ontology_id) |>
######## Obtain FAMILY ontologies
dplyr::left_join(ontology_slim_df, by = c("family" = "NBAtlas_label")) |>
dplyr::rename(family_ontology = CL_ontology_id) |>
# rename columns to start with `annot_type`
dplyr::rename_with(\(x) {paste(annot_type, x, sep = "_")}, -cell_id)
}
Read and prepare input data
Read merged SCE object:
merged_sce <- readRDS(sce_file)
# immediately remove assays we don't need for space
assay(merged_sce, "spliced") <- NULL
assay(merged_sce, "counts") <- NULL
reducedDim(merged_sce, "PCA") <- NULL
Read SingleR results:
singler_df <- singler_files |>
purrr::map(readr::read_tsv) |>
purrr::list_rbind(names_to = "library_id") |>
dplyr::mutate(
# add cell id column for unique rows & joining
cell_id = glue::glue("{library_id}-{barcodes}"),
# recode NA -> "Unknown" and NE -> "Neuroendocrine"
singler = dplyr::case_when(
is.na(pruned.labels) ~ "Unknown",
pruned.labels == "NE" ~ "Neuroendocrine",
.default = pruned.labels)
) |>
dplyr::select(cell_id, singler)
Read scANVI results:
scanvi_df <- readr::read_tsv(
file.path(scanvi_dir, "scanvi_predictions.tsv")
) |>
dplyr::select(
cell_id,
scanvi = scanvi_prediction,
starts_with("pp_")
) |>
tidyr::pivot_longer(
starts_with("pp_"),
names_to = "posterior_celltype",
values_to = "posterior"
) |>
dplyr::mutate(posterior_celltype = stringr::str_remove(posterior_celltype, "^pp_")) |>
dplyr::filter(scanvi == posterior_celltype) |>
# recode to Unknown if below the threshold, and NE -> Neuroendocrine
dplyr::mutate(scanvi = dplyr::case_when(
posterior < params$scanvi_pp_threshold ~ "Unknown",
scanvi == "NE" ~ "Neuroendocrine",
.default = scanvi)) |>
dplyr::select(cell_id, scanvi)
Read additional helper files:
palette_df <- readr::read_tsv(palette_file)
celltype_pal <- palette_df$hex_color
names(celltype_pal) <- palette_df$NBAtlas_label
label_map_df <- readr::read_tsv(nbatlas_label_map_file)
ontology_df <- readr::read_tsv(nbatlas_ontology_file)
nbatlas_markers_df <- readr::read_tsv(nbatlas_markers_file)
validation_df <- readr::read_tsv(validation_url) |>
dplyr::rename(
consensus = consensus_annotation,
consensus_family_label = validation_group_annotation,
consensus_family_ontology = validation_group_ontology
)
Combine annotations into a single data frame:
celltype_df <- scuttle::makePerCellDF(
merged_sce,
use.coldata = c("sample_id", "is_xenograft", "consensus_celltype_annotation", "consensus_celltype_ontology")
) |>
tibble::rownames_to_column("cell_id") |>
tidyr::separate(cell_id, into = c("library_id", "cell_barcode"), remove = FALSE) |>
# keep UMAP for eventual viz
dplyr::rename(
consensus = consensus_celltype_annotation,
consensus_ontology = consensus_celltype_ontology,
UMAP1 = UMAP.1,
UMAP2 = UMAP.2
) |>
dplyr::left_join(singler_df, by = "cell_id") |>
dplyr::left_join(scanvi_df, by = "cell_id")
# Pull out metadata we'll want later
sample_metadata <- celltype_df |>
dplyr::select(sample_id, library_id, is_xenograft) |>
unique()
Annotate
Firt we prepare the data frame for annotation.
ontology_slim_df <- ontology_df |>
dplyr::select(-CL_annotation)
singler_annotation_df <- prep_for_annotation(
celltype_df,
"singler",
ontology_slim_df,
label_map_df
)
scanvi_annotation_df <- prep_for_annotation(
celltype_df,
"scanvi",
ontology_slim_df,
label_map_df
)
annotation_df <- celltype_df |>
dplyr::select(cell_id, consensus, consensus_ontology) |>
dplyr::left_join(singler_annotation_df, by = "cell_id") |>
dplyr::left_join(scanvi_annotation_df, by = "cell_id") |>
dplyr::left_join(validation_df, by = c("consensus", "consensus_ontology")) |>
dplyr::select(cell_id, starts_with("consensus"), starts_with("singler"), starts_with("scanvi"))
In the code below, we compare ontology ids to derive final
annotations. However, based on those comparisons, we assign a final
label, not final ontology id, and then add the final ontology
ids back into those annotations. This is because we have several
NA ontology ids, and we would not be able to unambiguously
map their labels in if we assigned ontologies.
final_annotation_df <- annotation_df |>
dplyr::mutate(
# For all comparisons, make sure we aren't comparing NA to NA and getting TRUE
final_label = dplyr::case_when(
########### Check for exact match between SingleR/scANVI
!is.na(scanvi_label_ontology) & singler_label_ontology == scanvi_label_ontology ~ scanvi_label,
########### Check for family match between SingleR/scANVI
!is.na(scanvi_family_ontology) & singler_family_ontology == scanvi_family_ontology ~ scanvi_family,
########## Now use agreement with consensus to assign a label: first by label, and then by family
!is.na(consensus_ontology) & singler_label_ontology == consensus_ontology ~ singler_label,
!is.na(consensus_ontology) & scanvi_label_ontology == consensus_ontology ~ scanvi_label,
!is.na(consensus_family_ontology) & singler_family_ontology == consensus_family_ontology ~ singler_family,
!is.na(consensus_family_ontology) & scanvi_family_ontology == consensus_family_ontology ~ scanvi_family,
# Everything else is Unknown
.default = "Unknown"
)
) |>
# Now, we can bring map the final ontologies into the data frame
dplyr::inner_join(
ontology_df |> dplyr::rename(final_label = NBAtlas_label, final_id = CL_ontology_id),
by = "final_label"
) |>
# add tumor column for visualization
dplyr::mutate(cell_class = dplyr::case_when(
final_label == "Neuroendocrine" ~ "tumor",
final_label == "Unknown" ~ "unknown",
.default = "normal"
))
Explore final annotations
What fraction of cells have been annotated?
sum(!is.na(final_annotation_df$final_id)) / nrow(final_annotation_df)
[1] 0.8506399
What fraction of cells have been annotated per library?
frac_labeled_df <- final_annotation_df |>
tidyr::separate(cell_id, into = c("library_id", "barcode"), remove = FALSE) |>
dplyr::group_by(library_id) |>
dplyr::summarize(
frac_labeled = sum(!is.na(final_id))/dplyr::n(),
library_size = dplyr::n()
) |>
# get PDX logical for coloring
dplyr::inner_join(sample_metadata, by = "library_id")
p1 <- ggplot(frac_labeled_df) +
aes(x = frac_labeled) +
geom_histogram(bins = 40) +
labs(x = "Fraction of library labeled") +
theme_bw()
p2 <- ggplot(frac_labeled_df) +
aes(x = library_size, y = frac_labeled, color = is_xenograft) +
geom_point() +
labs(
x = "Total cells in library",
y = "Fraction of library labeled"
) +
theme_bw() +
theme(legend.position = "bottom")
p1 + p2

UMAPs
In this section, we show the UMAP from the merged (not
batch-corrected!) SCPCP000004 object colored by cell type
annotations and other potentially relevant metadata.
label_order <- label_map_df |>
dplyr::add_count(NBAtlas_family) |>
dplyr::arrange(desc(n), NBAtlas_family) |>
dplyr::filter(NBAtlas_label %in% final_annotation_df$final_label) |>
dplyr::pull(NBAtlas_label)
label_order <- c(label_order, "Unknown")
# prepare data frame for plotting
umap_df <- celltype_df |>
dplyr::select(cell_id, sample_id, library_id, is_xenograft, UMAP1, UMAP2) |>
dplyr::left_join(final_annotation_df, by = "cell_id") |>
dplyr::mutate(final_label = factor(final_label, levels = label_order))
Colored by cell type assignment
Note that in the UMAP below, cell types in these same “family” each
share a color:
- T cells
- Natural killer cells
- Conventional dendritic cells
- Monocytes
ggplot(umap_df) +
aes(x = UMAP1, y = UMAP2, color = final_label) +
geom_point(alpha = 0.2, size = 0.3) +
scale_color_manual(values = celltype_pal) +
guides(color = guide_legend(override.aes = list(size = 2, alpha = 1))) +
umap_theme +
theme(legend.position = "bottom")

Faceted by cell type assignment
This UMAP is the same as the previous one, except each facet
highlights one specific cell type. In this plot, only cell types with at
least 10 cells are shown.
umap_df |>
dplyr::add_count(final_label) |>
dplyr::filter(n > 10) |>
faceted_umap(
annotation_column = final_label,
celltype_colors = celltype_pal,
facet_wrap_cols = 6
) +
umap_theme +
theme(legend.position = "none")

Colored by tumor classification
ggplot(umap_df) +
aes(x = UMAP1, y = UMAP2, color = cell_class) +
geom_point(alpha = 0.2, size = 0.3) +
scale_color_brewer(palette = "Set2") +
guides(color = guide_legend(override.aes = list(size = 2, alpha = 1))) +
umap_theme

Colored by PDX status
ggplot(umap_df) +
aes(x = UMAP1, y = UMAP2, color = is_xenograft) +
geom_point(alpha = 0.2, size = 0.3) +
scale_color_brewer(palette = "Set1") +
guides(color = guide_legend(override.aes = list(size = 2, alpha = 1))) +
umap_theme

Dot plot
This section shows dot plots of marker gene expression for the final
annotations. Specifically, we show the top 5 highest log2FC
marker genes per NBAtlas cell type for validation. Marker genes are
taken directly from the NBAtlas paper where they were identified with
Seurat::FindMarkers().
Marker genes that are present in three or more cell types are not
included. In addition, the upper limit for the color scale has been
censored to a maximum of 6 to ensure visibility of expression levels
across all cell types.
input_dotplot_df <- final_annotation_df |>
dplyr::filter(final_label != "Unknown") |>
# recode cell types to match what is present in NBAtlas marker genes
dplyr::mutate(
label_recoded = dplyr::case_when(
stringr::str_detect(final_label, "monocyte") ~ "Monocyte",
stringr::str_detect(final_label, "cDC") ~ "cDC",
.default = final_label
)) |>
dplyr::select(cell_id, label_recoded)
# data frame of top 5 upregulated genes per cell type
top_nbatlas_markers_df <- subset_nbatlas_markers(nbatlas_markers_df, 5, "up") |>
# only markers in no more than 2 categories
# this is a good middle ground to ensure there are at least 2 markers per cell type
dplyr::add_count(ensembl_gene_id) |>
dplyr::filter(n < 3) |>
dplyr::select(-n)
# get total number of cells per final annotation group and set up y_label
total_cells_df <- input_dotplot_df |>
dplyr::count(label_recoded, name = "total_cells") |>
dplyr::mutate(y_label = glue::glue("{label_recoded} ({total_cells})")) |>
dplyr::inner_join(label_map_df, by = c("label_recoded" = "NBAtlas_label")) |>
dplyr::group_by(NBAtlas_family) |>
dplyr::mutate(family_total_cells = sum(total_cells)) |>
# order by family count, but put Unknown at the end
dplyr::arrange(desc(family_total_cells)) |>
# amazing new dplyr trick; only this row is TRUE, so it gets moved to the last row
dplyr::arrange(label_recoded == "Unknown")
total_cells_df$y_label <- factor(total_cells_df$y_label, levels = rev(total_cells_df$y_label))
nbatlas_bar_order <- total_cells_df$label_recoded
# Prepare the expressed_genes vector
# we only care about if that gene is expressed otherwise we won't waste memory and include it
gene_sums <- rowData(merged_sce) |>
as.data.frame() |>
dplyr::select(contains("detected")) |>
as.matrix() |>
rowSums()
expressed_genes <- names(gene_sums)[gene_sums > 0]
generate_dotplot(
merged_sce = merged_sce,
markers_df = top_nbatlas_markers_df,
celltype_df = input_dotplot_df,
total_cells_df = total_cells_df,
expressed_genes = expressed_genes,
bar_order = nbatlas_bar_order,
celltype_palette = celltype_pal,
min_cells = 10 # only show cell types with at least 10 cells
) +
theme(legend.position = "bottom")

Export
Export the final table with submission-ready column names.
final_annotation_df |>
dplyr::select(
cell_id,
cell_type_assignment = final_label,
tumor_cell_classification = cell_class,
CL_ontology_id = final_id,
CL_annotation
) |>
readr::write_tsv(
params$annotations_tsv
)
Session Info
sessionInfo()
R version 4.4.0 (2024-04-24)
Platform: aarch64-apple-darwin20
Running under: macOS 15.6
Matrix products: default
BLAS: /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib
LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib; LAPACK version 3.12.0
locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
time zone: America/New_York
tzcode source: internal
attached base packages:
[1] stats4 stats graphics grDevices datasets utils methods base
other attached packages:
[1] SingleCellExperiment_1.26.0 SummarizedExperiment_1.34.0 Biobase_2.64.0 GenomicRanges_1.56.2 GenomeInfoDb_1.40.1
[6] IRanges_2.38.1 S4Vectors_0.42.1 BiocGenerics_0.50.0 MatrixGenerics_1.16.0 matrixStats_1.5.0
[11] patchwork_1.3.0 ggplot2_3.5.2
loaded via a namespace (and not attached):
[1] gtable_0.3.6 xfun_0.52 lattice_0.22-6 tzdb_0.5.0 bitops_1.0-9
[6] vctrs_0.6.5 tools_4.4.0 generics_0.1.4 curl_6.3.0 parallel_4.4.0
[11] tibble_3.3.0 pkgconfig_2.0.3 Matrix_1.7-1 RColorBrewer_1.1-3 sparseMatrixStats_1.16.0
[16] lifecycle_1.0.4 GenomeInfoDbData_1.2.12 compiler_4.4.0 farver_2.1.2 stringr_1.5.1
[21] codetools_0.2-20 yaml_2.3.10 pillar_1.10.2 crayon_1.5.3 tidyr_1.3.1
[26] BiocParallel_1.38.0 DelayedArray_0.30.1 abind_1.4-8 tidyselect_1.2.1 stringi_1.8.7
[31] dplyr_1.1.4 purrr_1.0.4 labeling_0.4.3 rprojroot_2.0.4 grid_4.4.0
[36] cli_3.6.5 SparseArray_1.4.8 magrittr_2.0.3 S4Arrays_1.4.1 readr_2.1.5
[41] withr_3.0.2 DelayedMatrixStats_1.26.0 scales_1.4.0 UCSC.utils_1.0.0 bit64_4.6.0-1
[46] XVector_0.44.0 httr_1.4.7 jpeg_0.1-11 ggmap_4.0.1 bit_4.6.0
[51] png_0.1-8 hms_1.1.3 beachmat_2.20.0 evaluate_1.0.3 knitr_1.50
[56] viridisLite_0.4.2 rlang_1.1.6 Rcpp_1.0.14 glue_1.8.0 scuttle_1.14.0
[61] BiocManager_1.30.26 renv_1.1.3 vroom_1.6.5 jsonlite_2.0.0 plyr_1.8.9
[66] R6_2.6.1 zlibbioc_1.50.0
LS0tCnRpdGxlOiAiRGVyaXZlIGZpbmFsIGFubm90YXRpb25zIGJhc2VkIG9uIE5CQXRsYXMgcmVmZXJlbmNlIgphdXRob3I6ICJTdGVwaGFuaWUgSi4gU3BpZWxtYW4iCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKICAgIHRvY19kZXB0aDogMwogICAgY29kZV9mb2xkaW5nOiBoaWRlCnBhcmFtczoKICBzY2FudmlfcHBfdGhyZXNob2xkOiAwLjc1ICAgIAogIGFubm90YXRpb25zX3RzdjogIi4uL3Jlc3VsdHMvU0NQQ1AwMDAwMDQtYW5ub3RhdGlvbnMudHN2IgotLS0KCkluIHRoaXMgYW5hbHlzaXMgbW9kdWxlLCB3ZSBoYXZlIGFubm90YXRlZCBjZWxsIHR5cGVzIGluIGBTQ1BDUDAwMDAwNGAgc2FtcGxlcyB1c2luZyB0d28gbWV0aG9kczogYFNpbmdsZVJgIGFuZCBgc2NBTlZJL3NjQXJjaGVzYCwgYm90aCB1c2luZyB0aGUgTkJBdGxhcyBkYXRhc2V0IGFzIGEgcmVmZXJlbmNlLgpJbiB0aGlzIG5vdGVib29rLCB3ZSBkZXJpdmUgdGhlIGZpbmFsIGFubm90YXRpb25zIHVzaW5nIGluZm9ybWF0aW9uIGZyb20gdGhlIGBTaW5nbGVSYCBsYWJlbHMsIHRoZSBgc2NBTlZJL3NjQXJjaGVzYCBsYWJlbHMsIGFuZCBleGlzdGluZyBjb25zZW5zdXMgY2VsbCB0eXBlIGFubm90YXRpb25zLgpBbGwgZmluYWwgYW5ub3RhdGlvbnMgdXNlIGxhYmVscyBwcmVzZW50IGluIHRoZSBOQkF0bGFzIGRhdGFzZXQuCgpXZSB1c2UgdGhlIGZvbGxvd2luZyBhcHByb2FjaCB0byBkZXRlcm1pbmUgdGhlIGZpbmFsIGFubm90YXRpb24uCgpGaXJzdCwgd2UgcHJpb3JpdGl6ZSBpbmZlcmVuY2VzIHdpdGggYFNpbmdsZVJgIGFuZCBgc2NBTlZJL3NjQXJjaGVzYC4KSWYgdGhlc2UgbWV0aG9kcyByZXR1cm5lZCB0aGUgZXhhY3Qgc2FtZSBsYWJlbCwgd2UgYXNzaWduIHRoaXMgbGFiZWwuCgpOZXh0LCB3ZSBjb21wYXJlIGxhYmVscyBhY3Jvc3MgYWxsIHRocmVlIG1ldGhvZHM6IGlmIGF0IGxlYXN0IF90d28gb3V0IG9mIHRocmVlXyBtZXRob2RzIGFncmVlLCB3ZSBhc3NpZ24gdGhlIGFncmVlaW5nIGxhYmVsLgpOb3RlIHRoYXQgdGhpcyByZXF1aXJlcyBtYXBwaW5nIGNvbnNlbnN1cyBjZWxsIHR5cGVzIHRvIE5CQXRsYXMgbGFiZWxzLCB3aGljaCBpcyBvbmx5IHBvc3NpYmxlIGZvciB0aGVzZSBjZWxsIHR5cGVzOiBlbmRvdGhlbGlhbCBjZWxscywgZmlicm9ibGFzdHMsIGFuZCBpbW11bmUgY2VsbCB0eXBlcyBleGNlcHQgZm9yIHRoZSBOQkF0bGFzIGxhYmVscyBgUkJDc2AgYW5kIGBwRENgIHdoaWNoIGRvIG5vdCBoYXZlIGNvcnJlc3BvbmRpbmcgY29uc2Vuc3VzIGxhYmVscy4KCkluIHRoaXMgYXBwcm9hY2gsIHRoZXJlIGFyZSB0d28gd2F5cyB0aGF0IGNlbGwgdHlwZSBhbm5vdGF0aW9ucyBjYW4gYWdyZWUsIGFuZCB3ZSB0cmVhdCB0aGVtIGFzIGZvbGxvd3M6CgoqIENlbGwgdHlwZSBhbm5vdGF0aW9ucyBjYW4gaGF2ZSBkaXJlY3RseSBjb3JyZXNwb25kaW5nIGxhYmVscy4gRm9yIGV4YW1wbGU6CiAgKiBJZiB0aGUgY29uc2Vuc3VzIGNlbGwgdHlwZSBpcyBgZW5kb3RoZWxpYWwgY2VsbGAgYW5kIGF0IGxlYXN0IG9uZSBvZiB0aGUgTkJBdGxhcy1kZXJpdmVkIGFubm90YXRpb25zIGlzIGBFbmRvdGhlbGlhbGAsIHdlIGFzc2lnbiB0aGUgbGFiZWwgYEVuZG90aGVsaWFsYC4KKiBDZWxsIHR5cGUgYW5ub3RhdGlvbnMgY2FuIGRpZmZlciBidXQgYmUgaW4gdGhlIHNhbWUgImZhbWlseSIgb2YgY2VsbCB0eXBlcy4gRm9yIGV4YW1wbGU6CiAgKiBJZiB0aGUgY29uc2Vuc3VzIGNlbGwgdHlwZSBpcyBgbWF0dXJlIFQgY2VsbGAgYW5kIGF0IGxlYXN0IG9uZSBvZiB0aGUgTkJBdGxhcy1kZXJpdmVkIGFubm90YXRpb25zIGlzIGBDRDQrIFQgY2VsbGAsIHdlIGFzc2lnbiB0aGUgYnJvYWRlciBsYWJlbCBgVCBjZWxsYC4KICAqIElmIG9uZSBOQkF0bGFzLWRlcml2ZWQgYW5ub3RhdGlvbiBpcyBgUGF0cm9sbGluZyBtb25vY3l0ZWAgYW5kIHRoZSBvdGhlciBpcyBgQ2xhc3NpY2FsIG1vbm9jeXRlYCwgd2UgYXNzaWduIHRoZSBicm9hZGVyIGxhYmVsIGBNb25vY3l0ZWAuCgpXZSB1c2UgQ0wgb250b2xvZ3kgaWRzIHRvIGRldGVybWluZSBhZ3JlZW1lbnQgdGhyb3VnaG91dC4gCkZvciBtb3JlIGluZm9ybWF0aW9uIGFib3V0IGhvdyB0aGVzZSBvbnRvbG9neSBpZHMgd2VyZSBkZXRlcm1pbmVkIGZvciBOQkF0bGFzIGxhYmVscywgc2VlIGAuLi9yZWZlcmVuY2VzL1JFQURNRS5tZGAuCgpGb3IgdGhlIGZpbmFsIGV4cG9ydGVkIFRTViBmaWxlLCB3ZSBmdXJ0aGVyIGRlc2lnbmF0ZSBhbnkgY2VsbHMgbGFiZWxlZCBhcyBgTmV1cm9lbmRvY3JpbmVgIGFzIGB0dW1vcmAsIGZvbGxvd2luZyBob3cgdGhpcyBsYWJlbCBpcyBpbnRlcnByZXRlZCBpbiBOQkF0bGFzLgoKIyMgU2V0dXAKCmBgYHtyLCB3YXJuaW5nID0gRkFMU0V9CnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyh7CiAgbGlicmFyeShnZ3Bsb3QyKQogIGxpYnJhcnkocGF0Y2h3b3JrKQogIGxpYnJhcnkoU2luZ2xlQ2VsbEV4cGVyaW1lbnQpCn0pCgp0aGVtZV9zZXQoCiAgdGhlbWVfYncoKQopCnVtYXBfdGhlbWUgPC0gbGlzdCgKICB0aGVtZSgKICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLCAKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLCAKICAgIGFzcGVjdC5yYXRpbyA9IDEKICApCikKCiMgc2V0dGluZ3MKb3B0aW9ucygKICBkcGx5ci5zdW1tYXJpc2UuaW5mb3JtID0gRkFMU0UsIAogIHJlYWRyLnNob3dfY29sX3R5cGVzID0gRkFMU0UKKQpgYGAKCiMjIyBQYXRocwoKYGBge3IgYmFzZSBwYXRoc30KbW9kdWxlX2RpciA8LSBycHJvanJvb3Q6OmZpbmRfcm9vdChycHJvanJvb3Q6OmlzX3JlbnZfcHJvamVjdCkKcmVwb3NpdG9yeV9iYXNlIDwtIGZpbGUucGF0aChtb2R1bGVfZGlyLCAiLi4iLCAiLi4iKQoKZGF0YV9kaXIgPC0gZmlsZS5wYXRoKHJlcG9zaXRvcnlfYmFzZSwgImRhdGEiLCAiY3VycmVudCIsICJTQ1BDUDAwMDAwNCIpCm1lcmdlZF9kaXIgPC0gZmlsZS5wYXRoKHJlcG9zaXRvcnlfYmFzZSwgImRhdGEiLCAiY3VycmVudCIsICJyZXN1bHRzIiwgIm1lcmdlLXNjZSIsICJTQ1BDUDAwMDAwNCIpCnJlZl9kaXIgPC0gZmlsZS5wYXRoKG1vZHVsZV9kaXIsICJyZWZlcmVuY2VzIikKcmVzdWx0c19kaXIgPC0gZmlsZS5wYXRoKG1vZHVsZV9kaXIsICJyZXN1bHRzIikKc2luZ2xlcl9kaXIgPC0gZmlsZS5wYXRoKHJlc3VsdHNfZGlyLCAic2luZ2xlciIpCnNjYW52aV9kaXIgPC0gZmlsZS5wYXRoKHJlc3VsdHNfZGlyLCAic2NhbnZpIikKYGBgCgpgYGB7ciBmaWxlIHBhdGhzfQojIG1lcmdlZCBTQ0UgZmlsZQpzY2VfZmlsZSA8LSBmaWxlLnBhdGgoCiAgbWVyZ2VkX2RpciwKICAiU0NQQ1AwMDAwMDRfbWVyZ2VkLnJkcyIKKQoKIyBTaW5nbGVSIGZpbGVzCnNpbmdsZXJfZmlsZXMgPC0gbGlzdC5maWxlcygKICBwYXRoID0gc2luZ2xlcl9kaXIsCiAgcGF0dGVybiA9ICJfc2luZ2xlci1hbm5vdGF0aW9uc1xcLnRzdiIsCiAgcmVjdXJzaXZlID0gVFJVRSwKICBmdWxsLm5hbWVzID0gVFJVRQopIHw+CiAgIyBhZGQgbmFtZXMgYXMgbGlicmFyeSBpZAogIHB1cnJyOjpzZXRfbmFtZXMoCiAgICBcKHgpIHsKICAgICAgc3RyaW5ncjo6c3RyX3JlbW92ZShiYXNlbmFtZSh4KSwgIl9zaW5nbGVyLWFubm90YXRpb25zLnRzdiIpCiAgICB9CiAgKQoKIyBzY2FudmkgcHJlZGljdGlvbnMKc2NhbnZpX2ZpbGUgPC0gZmlsZS5wYXRoKAogIHNjYW52aV9kaXIsIAogICJzY2FudmktcHJlZGljdGlvbnMudHN2IgopCgojIHBhbGV0dGUgZmlsZQpwYWxldHRlX2ZpbGUgPC0gZmlsZS5wYXRoKAogIG1vZHVsZV9kaXIsCiAgInBhbGV0dGVzIiwKICAibmJhdGxhcy1jZWxsLXR5cGUtcGFsZXR0ZS50c3YiCikKCiMgbGFiZWwgbWFwIGZpbGUKbmJhdGxhc19sYWJlbF9tYXBfZmlsZSA8LSBmaWxlLnBhdGgoCiAgcmVmX2RpciwgCiAgIm5iYXRsYXMtbGFiZWwtbWFwLnRzdiIKKQoKIyBsYWJlbCBvbnRvbG9naWVzIGZpbGUKbmJhdGxhc19vbnRvbG9neV9maWxlIDwtIGZpbGUucGF0aCgKICByZWZfZGlyLCAKICAibmJhdGxhcy1vbnRvbG9neS1pZHMudHN2IgopCgojIG1hcmtlciBnZW5lcyBmcm9tIE5CQXRsYXMKbmJhdGxhc19tYXJrZXJzX2ZpbGUgPC0gZmlsZS5wYXRoKAogIHJlZl9kaXIsIAogICJuYmF0bGFzLW1hcmtlci1nZW5lcy50c3YiCikKCiMgdmFsaWRhdGlvbiBncm91cHMgVVJMLCB1c2VkIHRvIG9idGFpbiBjb25zZW5zdXMgImZhbWlseSIgb250b2xvZ2llcyAoYWthIGJyb2FkIHZhbGlkYXRpb24gZ3JvdXAgb250b2xvZ2llcykKdmFsaWRhdGlvbl91cmwgPC0gImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9BbGV4c0xlbW9uYWRlL09wZW5TY1BDQS1hbmFseXNpcy9yZWZzL2hlYWRzL21haW4vYW5hbHlzZXMvY2VsbC10eXBlLWNvbnNlbnN1cy9yZWZlcmVuY2VzL2NvbnNlbnN1cy12YWxpZGF0aW9uLWdyb3Vwcy50c3YiCmBgYAoKCiMjIyBGdW5jdGlvbnMKCmBgYHtyfQojIFNvdXJjZSB1dGlsaXRpZXMgZnVuY3Rpb25zOgojIC0gc3Vic2V0X25iYXRsYXNfbWFya2VycygpCiMgLSBmYWNldGVkX3VtYXAoKQojIC0gZ2VuZXJhdGVfZG90cGxvdCgpCnNvdXJjZShmaWxlLnBhdGgobW9kdWxlX2RpciwgInNjcmlwdHMiLCAidXRpbHMiLCAiY2VsbHR5cGUtdXRpbHMuUiIpKQpgYGAKCgpgYGB7cn0KCiMnIFByZXBhcmUgZGF0YSBmcmFtZSBvZiBzY0FOVkkgb3IgU2luZ2xlUiBsYWJlbHMgZm9yIGFubm90YXRpb24KIycKIycgQHBhcmFtIGRmIERhdGEgZnJhbWUgdG8gcHJlcGFyZQojJyBAcGFyYW0gYW5ub3RfdHlwZSAic2luZ2xlciIgb3IgInNjYW52aSIKIycgQHBhcmFtIG9udG9sb2d5X2RmIERhdGEgZnJhbWUgb2Ygb250b2xvZ3kgaWRzCiMnIEBwYXJhbSBsYWJlbF9tYXBfZGYgRGF0YSBmcmFtZSBtYXBwaW5nIGxhYmVscyB0byBmYW1pbHkgbGFiZWxzCiMnCiMnIEByZXR1cm5zIFdpZGUgZGF0YSBmcmFtZSB3aXRoIG9udG9sb2dpZXMgYW5kIGZhbWlseSBsYWJlbHMgZm9yIHRoZSBnaXZlbiBhbm5vdGF0aW9uIHR5cGUKcHJlcF9mb3JfYW5ub3RhdGlvbiA8LSBmdW5jdGlvbigKICAgIGRmLCAKICAgIGFubm90X3R5cGUsIAogICAgb250b2xvZ3lfZGYsIAogICAgbGFiZWxfbWFwX2RmKSB7CgogIGRmIHw+CiAgICBkcGx5cjo6c2VsZWN0KGFsbF9vZihjKCJjZWxsX2lkIiwgYW5ub3RfdHlwZSkpKSB8PgogICAgZHBseXI6OnJlbmFtZShsYWJlbCA9IGFubm90X3R5cGUpIHw+CiAgICAjIyMjIyMjIEpvaW4gaW4gdGhlIGZhbWlseSBsYWJlbHMKICAgIGRwbHlyOjpsZWZ0X2pvaW4obGFiZWxfbWFwX2RmLCBieSA9IGMoImxhYmVsIiA9ICJOQkF0bGFzX2xhYmVsIikpIHw+CiAgICBkcGx5cjo6cmVuYW1lKGZhbWlseSA9IE5CQXRsYXNfZmFtaWx5KSB8PgogICAgIyMjIyMjIyMjIE9idGFpbiBMQUJFTCBvbnRvbG9naWVzCiAgICBkcGx5cjo6bGVmdF9qb2luKG9udG9sb2d5X3NsaW1fZGYsIGJ5ID0gYygibGFiZWwiID0gIk5CQXRsYXNfbGFiZWwiKSkgfD4KICAgIGRwbHlyOjpyZW5hbWUobGFiZWxfb250b2xvZ3kgPSBDTF9vbnRvbG9neV9pZCkgfD4KICAgICMjIyMjIyMjIE9idGFpbiBGQU1JTFkgb250b2xvZ2llcwogICAgZHBseXI6OmxlZnRfam9pbihvbnRvbG9neV9zbGltX2RmLCBieSA9IGMoImZhbWlseSIgPSAiTkJBdGxhc19sYWJlbCIpKSB8PgogICAgZHBseXI6OnJlbmFtZShmYW1pbHlfb250b2xvZ3kgPSBDTF9vbnRvbG9neV9pZCkgfD4KICAgICMgcmVuYW1lIGNvbHVtbnMgdG8gc3RhcnQgd2l0aCBgYW5ub3RfdHlwZWAKICAgIGRwbHlyOjpyZW5hbWVfd2l0aChcKHgpIHtwYXN0ZShhbm5vdF90eXBlLCB4LCBzZXAgPSAiXyIpfSwgLWNlbGxfaWQpIAp9CmBgYAoKCiMjIyBSZWFkIGFuZCBwcmVwYXJlIGlucHV0IGRhdGEKClJlYWQgbWVyZ2VkIFNDRSBvYmplY3Q6CgpgYGB7cn0KbWVyZ2VkX3NjZSA8LSByZWFkUkRTKHNjZV9maWxlKQojIGltbWVkaWF0ZWx5IHJlbW92ZSBhc3NheXMgd2UgZG9uJ3QgbmVlZCBmb3Igc3BhY2UKYXNzYXkobWVyZ2VkX3NjZSwgInNwbGljZWQiKSA8LSBOVUxMCmFzc2F5KG1lcmdlZF9zY2UsICJjb3VudHMiKSA8LSBOVUxMCnJlZHVjZWREaW0obWVyZ2VkX3NjZSwgIlBDQSIpIDwtIE5VTEwKYGBgCgpSZWFkIGBTaW5nbGVSYCByZXN1bHRzOgoKYGBge3J9CnNpbmdsZXJfZGYgPC0gc2luZ2xlcl9maWxlcyB8PgogIHB1cnJyOjptYXAocmVhZHI6OnJlYWRfdHN2KSB8PgogIHB1cnJyOjpsaXN0X3JiaW5kKG5hbWVzX3RvID0gImxpYnJhcnlfaWQiKSB8PgogIGRwbHlyOjptdXRhdGUoCiAgICAjIGFkZCBjZWxsIGlkIGNvbHVtbiBmb3IgdW5pcXVlIHJvd3MgJiBqb2luaW5nCiAgICBjZWxsX2lkID0gZ2x1ZTo6Z2x1ZSgie2xpYnJhcnlfaWR9LXtiYXJjb2Rlc30iKSwKICAgICMgcmVjb2RlIE5BIC0+ICJVbmtub3duIiBhbmQgTkUgLT4gIk5ldXJvZW5kb2NyaW5lIgogICAgc2luZ2xlciA9IGRwbHlyOjpjYXNlX3doZW4oCiAgICAgIGlzLm5hKHBydW5lZC5sYWJlbHMpIH4gIlVua25vd24iLCAKICAgICAgcHJ1bmVkLmxhYmVscyA9PSAiTkUiIH4gIk5ldXJvZW5kb2NyaW5lIiwKICAgICAgLmRlZmF1bHQgPSBwcnVuZWQubGFiZWxzKQogICkgfD4KICBkcGx5cjo6c2VsZWN0KGNlbGxfaWQsIHNpbmdsZXIpCmBgYAoKUmVhZCBgc2NBTlZJYCByZXN1bHRzOgoKYGBge3J9CnNjYW52aV9kZiA8LSByZWFkcjo6cmVhZF90c3YoCiAgZmlsZS5wYXRoKHNjYW52aV9kaXIsICJzY2FudmlfcHJlZGljdGlvbnMudHN2IikKKSB8PgogIGRwbHlyOjpzZWxlY3QoCiAgICBjZWxsX2lkLCAKICAgIHNjYW52aSA9IHNjYW52aV9wcmVkaWN0aW9uLCAKICAgIHN0YXJ0c193aXRoKCJwcF8iKQogICkgfD4KICB0aWR5cjo6cGl2b3RfbG9uZ2VyKAogICAgc3RhcnRzX3dpdGgoInBwXyIpLCAKICAgIG5hbWVzX3RvID0gInBvc3Rlcmlvcl9jZWxsdHlwZSIsIAogICAgdmFsdWVzX3RvID0gInBvc3RlcmlvciIKICApIHw+CiAgZHBseXI6Om11dGF0ZShwb3N0ZXJpb3JfY2VsbHR5cGUgPSBzdHJpbmdyOjpzdHJfcmVtb3ZlKHBvc3Rlcmlvcl9jZWxsdHlwZSwgIl5wcF8iKSkgfD4KICBkcGx5cjo6ZmlsdGVyKHNjYW52aSA9PSBwb3N0ZXJpb3JfY2VsbHR5cGUpIHw+CiAgIyByZWNvZGUgdG8gVW5rbm93biBpZiBiZWxvdyB0aGUgdGhyZXNob2xkLCBhbmQgTkUgLT4gTmV1cm9lbmRvY3JpbmUKICBkcGx5cjo6bXV0YXRlKHNjYW52aSA9IGRwbHlyOjpjYXNlX3doZW4oCiAgICBwb3N0ZXJpb3IgPCBwYXJhbXMkc2NhbnZpX3BwX3RocmVzaG9sZCB+ICJVbmtub3duIiwgCiAgICBzY2FudmkgPT0gIk5FIiB+ICJOZXVyb2VuZG9jcmluZSIsIAogICAgLmRlZmF1bHQgPSBzY2FudmkpKSB8PgogIGRwbHlyOjpzZWxlY3QoY2VsbF9pZCwgc2NhbnZpKQpgYGAKClJlYWQgYWRkaXRpb25hbCBoZWxwZXIgZmlsZXM6CgpgYGB7cn0KcGFsZXR0ZV9kZiA8LSByZWFkcjo6cmVhZF90c3YocGFsZXR0ZV9maWxlKQpjZWxsdHlwZV9wYWwgPC0gcGFsZXR0ZV9kZiRoZXhfY29sb3IKbmFtZXMoY2VsbHR5cGVfcGFsKSA8LSBwYWxldHRlX2RmJE5CQXRsYXNfbGFiZWwKCmxhYmVsX21hcF9kZiA8LSByZWFkcjo6cmVhZF90c3YobmJhdGxhc19sYWJlbF9tYXBfZmlsZSkKb250b2xvZ3lfZGYgPC0gcmVhZHI6OnJlYWRfdHN2KG5iYXRsYXNfb250b2xvZ3lfZmlsZSkKCm5iYXRsYXNfbWFya2Vyc19kZiA8LSByZWFkcjo6cmVhZF90c3YobmJhdGxhc19tYXJrZXJzX2ZpbGUpCgp2YWxpZGF0aW9uX2RmIDwtIHJlYWRyOjpyZWFkX3Rzdih2YWxpZGF0aW9uX3VybCkgfD4KICBkcGx5cjo6cmVuYW1lKAogICAgY29uc2Vuc3VzID0gY29uc2Vuc3VzX2Fubm90YXRpb24sCiAgICBjb25zZW5zdXNfZmFtaWx5X2xhYmVsID0gdmFsaWRhdGlvbl9ncm91cF9hbm5vdGF0aW9uLAogICAgY29uc2Vuc3VzX2ZhbWlseV9vbnRvbG9neSA9IHZhbGlkYXRpb25fZ3JvdXBfb250b2xvZ3kgCiAgKQpgYGAKCgpDb21iaW5lIGFubm90YXRpb25zIGludG8gYSBzaW5nbGUgZGF0YSBmcmFtZToKCmBgYHtyfQpjZWxsdHlwZV9kZiA8LSBzY3V0dGxlOjptYWtlUGVyQ2VsbERGKAogIG1lcmdlZF9zY2UsCiAgdXNlLmNvbGRhdGEgPSBjKCJzYW1wbGVfaWQiLCAiaXNfeGVub2dyYWZ0IiwgImNvbnNlbnN1c19jZWxsdHlwZV9hbm5vdGF0aW9uIiwgImNvbnNlbnN1c19jZWxsdHlwZV9vbnRvbG9neSIpCikgfD4KICB0aWJibGU6OnJvd25hbWVzX3RvX2NvbHVtbigiY2VsbF9pZCIpIHw+CiAgdGlkeXI6OnNlcGFyYXRlKGNlbGxfaWQsIGludG8gPSBjKCJsaWJyYXJ5X2lkIiwgImNlbGxfYmFyY29kZSIpLCByZW1vdmUgPSBGQUxTRSkgfD4KICAjIGtlZXAgVU1BUCBmb3IgZXZlbnR1YWwgdml6CiAgZHBseXI6OnJlbmFtZSgKICAgIGNvbnNlbnN1cyA9IGNvbnNlbnN1c19jZWxsdHlwZV9hbm5vdGF0aW9uLCAKICAgIGNvbnNlbnN1c19vbnRvbG9neSA9IGNvbnNlbnN1c19jZWxsdHlwZV9vbnRvbG9neSwKICAgIFVNQVAxID0gVU1BUC4xLCAKICAgIFVNQVAyID0gVU1BUC4yCiAgKSB8PgogIGRwbHlyOjpsZWZ0X2pvaW4oc2luZ2xlcl9kZiwgYnkgPSAiY2VsbF9pZCIpIHw+CiAgZHBseXI6OmxlZnRfam9pbihzY2FudmlfZGYsIGJ5ID0gImNlbGxfaWQiKQoKCiMgUHVsbCBvdXQgbWV0YWRhdGEgd2UnbGwgd2FudCBsYXRlcgpzYW1wbGVfbWV0YWRhdGEgPC0gY2VsbHR5cGVfZGYgfD4KICBkcGx5cjo6c2VsZWN0KHNhbXBsZV9pZCwgbGlicmFyeV9pZCwgaXNfeGVub2dyYWZ0KSB8PgogIHVuaXF1ZSgpCmBgYAoKCiMjIEFubm90YXRlCgpGaXJ0IHdlIHByZXBhcmUgdGhlIGRhdGEgZnJhbWUgZm9yIGFubm90YXRpb24uCgpgYGB7ciwgd2FybmluZyA9IEZBTFNFfQpvbnRvbG9neV9zbGltX2RmIDwtIG9udG9sb2d5X2RmIHw+CiAgZHBseXI6OnNlbGVjdCgtQ0xfYW5ub3RhdGlvbikKCnNpbmdsZXJfYW5ub3RhdGlvbl9kZiA8LSBwcmVwX2Zvcl9hbm5vdGF0aW9uKAogIGNlbGx0eXBlX2RmLCAKICAic2luZ2xlciIsIAogIG9udG9sb2d5X3NsaW1fZGYsIAogIGxhYmVsX21hcF9kZgopCnNjYW52aV9hbm5vdGF0aW9uX2RmIDwtIHByZXBfZm9yX2Fubm90YXRpb24oCiAgY2VsbHR5cGVfZGYsIAogICJzY2FudmkiLCAKICBvbnRvbG9neV9zbGltX2RmLCAKICBsYWJlbF9tYXBfZGYKKQoKYW5ub3RhdGlvbl9kZiA8LSBjZWxsdHlwZV9kZiB8PgogIGRwbHlyOjpzZWxlY3QoY2VsbF9pZCwgY29uc2Vuc3VzLCBjb25zZW5zdXNfb250b2xvZ3kpIHw+CiAgZHBseXI6OmxlZnRfam9pbihzaW5nbGVyX2Fubm90YXRpb25fZGYsIGJ5ID0gImNlbGxfaWQiKSB8PgogIGRwbHlyOjpsZWZ0X2pvaW4oc2NhbnZpX2Fubm90YXRpb25fZGYsIGJ5ID0gImNlbGxfaWQiKSB8PgogIGRwbHlyOjpsZWZ0X2pvaW4odmFsaWRhdGlvbl9kZiwgYnkgPSBjKCJjb25zZW5zdXMiLCAiY29uc2Vuc3VzX29udG9sb2d5IikpIHw+CiAgZHBseXI6OnNlbGVjdChjZWxsX2lkLCBzdGFydHNfd2l0aCgiY29uc2Vuc3VzIiksIHN0YXJ0c193aXRoKCJzaW5nbGVyIiksIHN0YXJ0c193aXRoKCJzY2FudmkiKSkKYGBgCgoKSW4gdGhlIGNvZGUgYmVsb3csIHdlIGNvbXBhcmUgb250b2xvZ3kgaWRzIHRvIGRlcml2ZSBmaW5hbCBhbm5vdGF0aW9ucy4KSG93ZXZlciwgYmFzZWQgb24gdGhvc2UgY29tcGFyaXNvbnMsIHdlIGFzc2lnbiBhIGZpbmFsIF9sYWJlbF8sIG5vdCBmaW5hbCBvbnRvbG9neSBpZCwgYW5kIHRoZW4gYWRkIHRoZSBmaW5hbCBvbnRvbG9neSBpZHMgYmFjayBpbnRvIHRob3NlIGFubm90YXRpb25zLgpUaGlzIGlzIGJlY2F1c2Ugd2UgaGF2ZSBzZXZlcmFsIGBOQWAgb250b2xvZ3kgaWRzLCBhbmQgd2Ugd291bGQgbm90IGJlIGFibGUgdG8gdW5hbWJpZ3VvdXNseSBtYXAgdGhlaXIgbGFiZWxzIGluIGlmIHdlIGFzc2lnbmVkIG9udG9sb2dpZXMuCgpgYGB7cn0KZmluYWxfYW5ub3RhdGlvbl9kZiA8LSBhbm5vdGF0aW9uX2RmIHw+IAogIGRwbHlyOjptdXRhdGUoCiAgICAjIEZvciBhbGwgY29tcGFyaXNvbnMsIG1ha2Ugc3VyZSB3ZSBhcmVuJ3QgY29tcGFyaW5nIE5BIHRvIE5BIGFuZCBnZXR0aW5nIFRSVUUKICAgIGZpbmFsX2xhYmVsID0gZHBseXI6OmNhc2Vfd2hlbigKICAgICAgIyMjIyMjIyMjIyMgQ2hlY2sgZm9yIGV4YWN0IG1hdGNoIGJldHdlZW4gU2luZ2xlUi9zY0FOVkkKICAgICAgIWlzLm5hKHNjYW52aV9sYWJlbF9vbnRvbG9neSkgJiBzaW5nbGVyX2xhYmVsX29udG9sb2d5ID09IHNjYW52aV9sYWJlbF9vbnRvbG9neSB+IHNjYW52aV9sYWJlbCwgCiAgICAgICMjIyMjIyMjIyMjIENoZWNrIGZvciBmYW1pbHkgbWF0Y2ggYmV0d2VlbiBTaW5nbGVSL3NjQU5WSQogICAgICAhaXMubmEoc2NhbnZpX2ZhbWlseV9vbnRvbG9neSkgJiBzaW5nbGVyX2ZhbWlseV9vbnRvbG9neSA9PSBzY2FudmlfZmFtaWx5X29udG9sb2d5IH4gc2NhbnZpX2ZhbWlseSwKICAgICAgIyMjIyMjIyMjIyBOb3cgdXNlIGFncmVlbWVudCB3aXRoIGNvbnNlbnN1cyB0byBhc3NpZ24gYSBsYWJlbDogZmlyc3QgYnkgbGFiZWwsIGFuZCB0aGVuIGJ5IGZhbWlseQogICAgICAhaXMubmEoY29uc2Vuc3VzX29udG9sb2d5KSAmIHNpbmdsZXJfbGFiZWxfb250b2xvZ3kgID09IGNvbnNlbnN1c19vbnRvbG9neSB+IHNpbmdsZXJfbGFiZWwsCiAgICAgICFpcy5uYShjb25zZW5zdXNfb250b2xvZ3kpICYgc2NhbnZpX2xhYmVsX29udG9sb2d5ICAgPT0gY29uc2Vuc3VzX29udG9sb2d5IH4gc2NhbnZpX2xhYmVsLAogICAgICAhaXMubmEoY29uc2Vuc3VzX2ZhbWlseV9vbnRvbG9neSkgJiBzaW5nbGVyX2ZhbWlseV9vbnRvbG9neSA9PSBjb25zZW5zdXNfZmFtaWx5X29udG9sb2d5IH4gc2luZ2xlcl9mYW1pbHksCiAgICAgICFpcy5uYShjb25zZW5zdXNfZmFtaWx5X29udG9sb2d5KSAmIHNjYW52aV9mYW1pbHlfb250b2xvZ3kgID09IGNvbnNlbnN1c19mYW1pbHlfb250b2xvZ3kgfiBzY2FudmlfZmFtaWx5LAogICAgICAjIEV2ZXJ5dGhpbmcgZWxzZSBpcyBVbmtub3duCiAgICAgIC5kZWZhdWx0ID0gIlVua25vd24iCiAgICApCiAgKSB8PgogICMgTm93LCB3ZSBjYW4gYnJpbmcgbWFwIHRoZSBmaW5hbCBvbnRvbG9naWVzIGludG8gdGhlIGRhdGEgZnJhbWUKICBkcGx5cjo6aW5uZXJfam9pbigKICAgIG9udG9sb2d5X2RmIHw+IGRwbHlyOjpyZW5hbWUoZmluYWxfbGFiZWwgPSBOQkF0bGFzX2xhYmVsLCBmaW5hbF9pZCA9IENMX29udG9sb2d5X2lkKSwgCiAgICBieSA9ICJmaW5hbF9sYWJlbCIKICApIHw+CiAgIyBhZGQgdHVtb3IgY29sdW1uIGZvciB2aXN1YWxpemF0aW9uCiAgZHBseXI6Om11dGF0ZShjZWxsX2NsYXNzID0gZHBseXI6OmNhc2Vfd2hlbigKICAgIGZpbmFsX2xhYmVsID09ICJOZXVyb2VuZG9jcmluZSIgfiAidHVtb3IiLCAKICAgIGZpbmFsX2xhYmVsID09ICJVbmtub3duIiB+ICJ1bmtub3duIiwgCiAgICAuZGVmYXVsdCA9ICJub3JtYWwiCiAgKSkKYGBgIAoKCiMjIEV4cGxvcmUgZmluYWwgYW5ub3RhdGlvbnMKCldoYXQgZnJhY3Rpb24gb2YgY2VsbHMgaGF2ZSBiZWVuIGFubm90YXRlZD8KCgpgYGB7cn0Kc3VtKCFpcy5uYShmaW5hbF9hbm5vdGF0aW9uX2RmJGZpbmFsX2lkKSkgLyBucm93KGZpbmFsX2Fubm90YXRpb25fZGYpCmBgYAoKCldoYXQgZnJhY3Rpb24gb2YgY2VsbHMgaGF2ZSBiZWVuIGFubm90YXRlZCBwZXIgbGlicmFyeT8KCmBgYHtyLCBmaWcud2lkdGggPSA4LCBmaWcuaGVpZ2h0ID0gNH0KZnJhY19sYWJlbGVkX2RmIDwtIGZpbmFsX2Fubm90YXRpb25fZGYgfD4KICB0aWR5cjo6c2VwYXJhdGUoY2VsbF9pZCwgaW50byA9IGMoImxpYnJhcnlfaWQiLCAiYmFyY29kZSIpLCByZW1vdmUgPSBGQUxTRSkgfD4KICBkcGx5cjo6Z3JvdXBfYnkobGlicmFyeV9pZCkgfD4KICBkcGx5cjo6c3VtbWFyaXplKAogICAgZnJhY19sYWJlbGVkID0gc3VtKCFpcy5uYShmaW5hbF9pZCkpL2RwbHlyOjpuKCksIAogICAgbGlicmFyeV9zaXplID0gZHBseXI6Om4oKQogICkgfD4KICAjIGdldCBQRFggbG9naWNhbCBmb3IgY29sb3JpbmcKICBkcGx5cjo6aW5uZXJfam9pbihzYW1wbGVfbWV0YWRhdGEsIGJ5ID0gImxpYnJhcnlfaWQiKQoKCnAxIDwtIGdncGxvdChmcmFjX2xhYmVsZWRfZGYpICsgCiAgYWVzKHggPSBmcmFjX2xhYmVsZWQpICsgCiAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDQwKSArCiAgbGFicyh4ID0gIkZyYWN0aW9uIG9mIGxpYnJhcnkgbGFiZWxlZCIpICsKICB0aGVtZV9idygpCgoKcDIgPC0gZ2dwbG90KGZyYWNfbGFiZWxlZF9kZikgKyAKICBhZXMoeCA9IGxpYnJhcnlfc2l6ZSwgeSA9IGZyYWNfbGFiZWxlZCwgY29sb3IgPSBpc194ZW5vZ3JhZnQpICsgCiAgZ2VvbV9wb2ludCgpICsgCiAgbGFicygKICAgIHggPSAiVG90YWwgY2VsbHMgaW4gbGlicmFyeSIsCiAgICB5ID0gIkZyYWN0aW9uIG9mIGxpYnJhcnkgbGFiZWxlZCIKICApICsKICB0aGVtZV9idygpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikKCgpwMSArIHAyCmBgYAoKIyMjIFVNQVBzCgpJbiB0aGlzIHNlY3Rpb24sIHdlIHNob3cgdGhlIFVNQVAgZnJvbSB0aGUgbWVyZ2VkIChub3QgYmF0Y2gtY29ycmVjdGVkISkgYFNDUENQMDAwMDA0YCBvYmplY3QgY29sb3JlZCBieSBjZWxsIHR5cGUgYW5ub3RhdGlvbnMgYW5kIG90aGVyIHBvdGVudGlhbGx5IHJlbGV2YW50IG1ldGFkYXRhLgoKCmBgYHtyfQpsYWJlbF9vcmRlciA8LSBsYWJlbF9tYXBfZGYgfD4KICBkcGx5cjo6YWRkX2NvdW50KE5CQXRsYXNfZmFtaWx5KSB8PgogIGRwbHlyOjphcnJhbmdlKGRlc2MobiksIE5CQXRsYXNfZmFtaWx5KSB8PgogIGRwbHlyOjpmaWx0ZXIoTkJBdGxhc19sYWJlbCAlaW4lIGZpbmFsX2Fubm90YXRpb25fZGYkZmluYWxfbGFiZWwpIHw+CiAgZHBseXI6OnB1bGwoTkJBdGxhc19sYWJlbCkKbGFiZWxfb3JkZXIgPC0gYyhsYWJlbF9vcmRlciwgIlVua25vd24iKQoKIyBwcmVwYXJlIGRhdGEgZnJhbWUgZm9yIHBsb3R0aW5nCnVtYXBfZGYgPC0gY2VsbHR5cGVfZGYgfD4KICBkcGx5cjo6c2VsZWN0KGNlbGxfaWQsIHNhbXBsZV9pZCwgbGlicmFyeV9pZCwgaXNfeGVub2dyYWZ0LCBVTUFQMSwgVU1BUDIpIHw+CiAgZHBseXI6OmxlZnRfam9pbihmaW5hbF9hbm5vdGF0aW9uX2RmLCBieSA9ICJjZWxsX2lkIikgfD4KICBkcGx5cjo6bXV0YXRlKGZpbmFsX2xhYmVsID0gZmFjdG9yKGZpbmFsX2xhYmVsLCBsZXZlbHMgPSBsYWJlbF9vcmRlcikpCmBgYAoKCiMjIyMgQ29sb3JlZCBieSBjZWxsIHR5cGUgYXNzaWdubWVudAoKTm90ZSB0aGF0IGluIHRoZSBVTUFQIGJlbG93LCBjZWxsIHR5cGVzIGluIHRoZXNlIHNhbWUgImZhbWlseSIgZWFjaCBzaGFyZSBhIGNvbG9yOgoKKiBUIGNlbGxzCiogTmF0dXJhbCBraWxsZXIgY2VsbHMKKiBDb252ZW50aW9uYWwgZGVuZHJpdGljIGNlbGxzCiogTW9ub2N5dGVzCgpgYGB7ciBmaWcud2lkdGggPSAxMCwgZmlnLmhlaWdodCA9IDEwfQpnZ3Bsb3QodW1hcF9kZikgKwogIGFlcyh4ID0gVU1BUDEsIHkgPSBVTUFQMiwgY29sb3IgPSBmaW5hbF9sYWJlbCkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjIsIHNpemUgPSAwLjMpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gY2VsbHR5cGVfcGFsKSArCiAgZ3VpZGVzKGNvbG9yID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZSA9IDIsIGFscGhhID0gMSkpKSArIAogIHVtYXBfdGhlbWUgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKQpgYGAKCiMjIyMgRmFjZXRlZCBieSBjZWxsIHR5cGUgYXNzaWdubWVudAoKVGhpcyBVTUFQIGlzIHRoZSBzYW1lIGFzIHRoZSBwcmV2aW91cyBvbmUsIGV4Y2VwdCBlYWNoIGZhY2V0IGhpZ2hsaWdodHMgb25lIHNwZWNpZmljIGNlbGwgdHlwZS4KSW4gdGhpcyBwbG90LCBvbmx5IGNlbGwgdHlwZXMgd2l0aCBhdCBsZWFzdCAxMCBjZWxscyBhcmUgc2hvd24uCgoKYGBge3IgZmlnLndpZHRoID0gMTYsIGZpZy5oZWlnaHQgPSAxNn0KdW1hcF9kZiB8PgogIGRwbHlyOjphZGRfY291bnQoZmluYWxfbGFiZWwpIHw+CiAgZHBseXI6OmZpbHRlcihuID4gMTApIHw+CiAgZmFjZXRlZF91bWFwKAogICAgYW5ub3RhdGlvbl9jb2x1bW4gPSBmaW5hbF9sYWJlbCwKICAgIGNlbGx0eXBlX2NvbG9ycyA9IGNlbGx0eXBlX3BhbCwgCiAgICBmYWNldF93cmFwX2NvbHMgPSA2CiAgKSArIAogICAgdW1hcF90aGVtZSArCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCmBgYAoKCiMjIyMgQ29sb3JlZCBieSB0dW1vciBjbGFzc2lmaWNhdGlvbgoKCmBgYHtyIGZpZy53aWR0aCA9IDgsIGZpZy5oZWlnaHQgPSA4fQpnZ3Bsb3QodW1hcF9kZikgKwogIGFlcyh4ID0gVU1BUDEsIHkgPSBVTUFQMiwgY29sb3IgPSBjZWxsX2NsYXNzKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuMiwgc2l6ZSA9IDAuMykgKwogIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlID0gIlNldDIiKSArCiAgZ3VpZGVzKGNvbG9yID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZSA9IDIsIGFscGhhID0gMSkpKSArCiAgdW1hcF90aGVtZQpgYGAKCiMjIyMgQ29sb3JlZCBieSBQRFggc3RhdHVzCgpgYGB7ciBmaWcud2lkdGggPSA4LCBmaWcuaGVpZ2h0ID0gOH0KZ2dwbG90KHVtYXBfZGYpICsKICBhZXMoeCA9IFVNQVAxLCB5ID0gVU1BUDIsIGNvbG9yID0gaXNfeGVub2dyYWZ0KSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuMiwgc2l6ZSA9IDAuMykgKwogIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlID0gIlNldDEiKSArCiAgZ3VpZGVzKGNvbG9yID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZSA9IDIsIGFscGhhID0gMSkpKSArCiAgdW1hcF90aGVtZQpgYGAKCgojIyMgRG90IHBsb3QKClRoaXMgc2VjdGlvbiBzaG93cyBkb3QgcGxvdHMgb2YgbWFya2VyIGdlbmUgZXhwcmVzc2lvbiBmb3IgdGhlIGZpbmFsIGFubm90YXRpb25zLgpTcGVjaWZpY2FsbHksIHdlIHNob3cgdGhlIHRvcCA1IGhpZ2hlc3QgYGxvZzJGQ2AgbWFya2VyIGdlbmVzIHBlciBOQkF0bGFzIGNlbGwgdHlwZSBmb3IgdmFsaWRhdGlvbi4KTWFya2VyIGdlbmVzIGFyZSB0YWtlbiBkaXJlY3RseSBmcm9tIHRoZSBOQkF0bGFzIHBhcGVyIHdoZXJlIHRoZXkgd2VyZSBpZGVudGlmaWVkIHdpdGggYFNldXJhdDo6RmluZE1hcmtlcnMoKWAuIAoKTWFya2VyIGdlbmVzIHRoYXQgYXJlIHByZXNlbnQgaW4gdGhyZWUgb3IgbW9yZSBjZWxsIHR5cGVzIGFyZSBub3QgaW5jbHVkZWQuCkluIGFkZGl0aW9uLCB0aGUgdXBwZXIgbGltaXQgZm9yIHRoZSBjb2xvciBzY2FsZSBoYXMgYmVlbiBjZW5zb3JlZCB0byBhIG1heGltdW0gb2YgNiB0byBlbnN1cmUgdmlzaWJpbGl0eSBvZiBleHByZXNzaW9uIGxldmVscyBhY3Jvc3MgYWxsIGNlbGwgdHlwZXMuCgpgYGB7cn0KaW5wdXRfZG90cGxvdF9kZiA8LSBmaW5hbF9hbm5vdGF0aW9uX2RmIHw+CiAgZHBseXI6OmZpbHRlcihmaW5hbF9sYWJlbCAhPSAiVW5rbm93biIpIHw+CiAgIyByZWNvZGUgY2VsbCB0eXBlcyB0byBtYXRjaCB3aGF0IGlzIHByZXNlbnQgaW4gTkJBdGxhcyBtYXJrZXIgZ2VuZXMKICBkcGx5cjo6bXV0YXRlKAogICAgbGFiZWxfcmVjb2RlZCA9IGRwbHlyOjpjYXNlX3doZW4oCiAgICAgIHN0cmluZ3I6OnN0cl9kZXRlY3QoZmluYWxfbGFiZWwsICJtb25vY3l0ZSIpIH4gIk1vbm9jeXRlIiwgCiAgICAgIHN0cmluZ3I6OnN0cl9kZXRlY3QoZmluYWxfbGFiZWwsICJjREMiKSB+ICJjREMiLAogICAgICAuZGVmYXVsdCA9IGZpbmFsX2xhYmVsCiAgKSkgfD4KICBkcGx5cjo6c2VsZWN0KGNlbGxfaWQsIGxhYmVsX3JlY29kZWQpCgojIGRhdGEgZnJhbWUgb2YgdG9wIDUgdXByZWd1bGF0ZWQgZ2VuZXMgcGVyIGNlbGwgdHlwZQp0b3BfbmJhdGxhc19tYXJrZXJzX2RmIDwtIHN1YnNldF9uYmF0bGFzX21hcmtlcnMobmJhdGxhc19tYXJrZXJzX2RmLCA1LCAidXAiKSB8PgogICMgb25seSBtYXJrZXJzIGluIG5vIG1vcmUgdGhhbiAyIGNhdGVnb3JpZXMKICAjIHRoaXMgaXMgYSBnb29kIG1pZGRsZSBncm91bmQgdG8gZW5zdXJlIHRoZXJlIGFyZSBhdCBsZWFzdCAyIG1hcmtlcnMgcGVyIGNlbGwgdHlwZQogIGRwbHlyOjphZGRfY291bnQoZW5zZW1ibF9nZW5lX2lkKSB8PgogIGRwbHlyOjpmaWx0ZXIobiA8IDMpIHw+CiAgZHBseXI6OnNlbGVjdCgtbikgCmBgYAoKCmBgYHtyfQojIGdldCB0b3RhbCBudW1iZXIgb2YgY2VsbHMgcGVyIGZpbmFsIGFubm90YXRpb24gZ3JvdXAgYW5kIHNldCB1cCB5X2xhYmVsCnRvdGFsX2NlbGxzX2RmIDwtIGlucHV0X2RvdHBsb3RfZGYgfD4KICBkcGx5cjo6Y291bnQobGFiZWxfcmVjb2RlZCwgbmFtZSA9ICJ0b3RhbF9jZWxscyIpIHw+CiAgZHBseXI6Om11dGF0ZSh5X2xhYmVsID0gZ2x1ZTo6Z2x1ZSgie2xhYmVsX3JlY29kZWR9ICh7dG90YWxfY2VsbHN9KSIpKSB8PgogIGRwbHlyOjppbm5lcl9qb2luKGxhYmVsX21hcF9kZiwgYnkgPSBjKCJsYWJlbF9yZWNvZGVkIiA9ICJOQkF0bGFzX2xhYmVsIikpIHw+CiAgZHBseXI6Omdyb3VwX2J5KE5CQXRsYXNfZmFtaWx5KSB8PgogIGRwbHlyOjptdXRhdGUoZmFtaWx5X3RvdGFsX2NlbGxzID0gc3VtKHRvdGFsX2NlbGxzKSkgfD4KICAjIG9yZGVyIGJ5IGZhbWlseSBjb3VudCwgYnV0IHB1dCBVbmtub3duIGF0IHRoZSBlbmQKICBkcGx5cjo6YXJyYW5nZShkZXNjKGZhbWlseV90b3RhbF9jZWxscykpIHw+CiAgIyBhbWF6aW5nIG5ldyBkcGx5ciB0cmljazsgb25seSB0aGlzIHJvdyBpcyBUUlVFLCBzbyBpdCBnZXRzIG1vdmVkIHRvIHRoZSBsYXN0IHJvdwogIGRwbHlyOjphcnJhbmdlKGxhYmVsX3JlY29kZWQgPT0gIlVua25vd24iKQoKdG90YWxfY2VsbHNfZGYkeV9sYWJlbCA8LSBmYWN0b3IodG90YWxfY2VsbHNfZGYkeV9sYWJlbCwgbGV2ZWxzID0gcmV2KHRvdGFsX2NlbGxzX2RmJHlfbGFiZWwpKQpuYmF0bGFzX2Jhcl9vcmRlciA8LSB0b3RhbF9jZWxsc19kZiRsYWJlbF9yZWNvZGVkCmBgYAoKYGBge3J9CiMgUHJlcGFyZSB0aGUgZXhwcmVzc2VkX2dlbmVzIHZlY3RvcgojIHdlIG9ubHkgY2FyZSBhYm91dCBpZiB0aGF0IGdlbmUgaXMgZXhwcmVzc2VkIG90aGVyd2lzZSB3ZSB3b24ndCB3YXN0ZSBtZW1vcnkgYW5kIGluY2x1ZGUgaXQKZ2VuZV9zdW1zIDwtIHJvd0RhdGEobWVyZ2VkX3NjZSkgfD4KICBhcy5kYXRhLmZyYW1lKCkgfD4KICBkcGx5cjo6c2VsZWN0KGNvbnRhaW5zKCJkZXRlY3RlZCIpKSB8PgogIGFzLm1hdHJpeCgpIHw+CiAgcm93U3VtcygpCmV4cHJlc3NlZF9nZW5lcyA8LSBuYW1lcyhnZW5lX3N1bXMpW2dlbmVfc3VtcyA+IDBdCmBgYAoKYGBge3IsIGZpZy53aWR0aCA9IDMyLCBmaWcuaGVpZ2h0ID0gMTZ9CmdlbmVyYXRlX2RvdHBsb3QoCiAgbWVyZ2VkX3NjZSA9IG1lcmdlZF9zY2UsCiAgbWFya2Vyc19kZiA9IHRvcF9uYmF0bGFzX21hcmtlcnNfZGYsCiAgY2VsbHR5cGVfZGYgPSBpbnB1dF9kb3RwbG90X2RmLAogIHRvdGFsX2NlbGxzX2RmID0gdG90YWxfY2VsbHNfZGYsCiAgZXhwcmVzc2VkX2dlbmVzID0gZXhwcmVzc2VkX2dlbmVzLAogIGJhcl9vcmRlciA9IG5iYXRsYXNfYmFyX29yZGVyLCAKICBjZWxsdHlwZV9wYWxldHRlID0gY2VsbHR5cGVfcGFsLCAKICBtaW5fY2VsbHMgPSAxMCAjIG9ubHkgc2hvdyBjZWxsIHR5cGVzIHdpdGggYXQgbGVhc3QgMTAgY2VsbHMKKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpCmBgYAoKCiMjIEV4cG9ydAoKRXhwb3J0IHRoZSBmaW5hbCB0YWJsZSB3aXRoIHN1Ym1pc3Npb24tcmVhZHkgY29sdW1uIG5hbWVzLgoKYGBge3J9CmZpbmFsX2Fubm90YXRpb25fZGYgfD4KICBkcGx5cjo6c2VsZWN0KAogICAgY2VsbF9pZCwgCiAgICBjZWxsX3R5cGVfYXNzaWdubWVudCA9IGZpbmFsX2xhYmVsLCAKICAgIHR1bW9yX2NlbGxfY2xhc3NpZmljYXRpb24gPSBjZWxsX2NsYXNzLAogICAgQ0xfb250b2xvZ3lfaWQgPSBmaW5hbF9pZCwgCiAgICBDTF9hbm5vdGF0aW9uCiAgKSB8PgogIHJlYWRyOjp3cml0ZV90c3YoCiAgICBwYXJhbXMkYW5ub3RhdGlvbnNfdHN2CiAgKQpgYGAKCiMjIFNlc3Npb24gSW5mbwoKYGBge3J9CnNlc3Npb25JbmZvKCkKYGBgCg==